﻿using FreeSql;
using FreeSql.DatabaseModel;
using Mohe.Scada.Util.Helper;
using Mohe.Scada.WebApi.Models;
using System.Reflection;

namespace Mohe.Scada.WebApi.CodeGen
{
    public class CodeGenerator
    {

        /// <summary>
        /// 代码生成器配置
        /// </summary>
        private static IFreeSql appDb;
        private static string dataBase = string.Empty;
        private static string solutionDir = string.Empty;

        static string GetSolutionRoot()
        {
            string solutionFilePath = null;
            string currentAssemblyLocation = Assembly.GetExecutingAssembly().Location;
            string currentDirectory = Path.GetDirectoryName(currentAssemblyLocation);

            // 从当前目录向上查找直到找到.sln文件
            while (solutionFilePath == null && currentDirectory != null)
            {
                string[] solutionFiles = Directory.GetFiles(currentDirectory, "*.sln", SearchOption.TopDirectoryOnly);
                if (solutionFiles.Length > 0)
                {
                    solutionFilePath = solutionFiles[0];
                }
                else
                {
                    currentDirectory = Directory.GetParent(currentDirectory)?.FullName;
                }
            }

            return solutionFilePath != null ? Path.GetDirectoryName(solutionFilePath) : null;
        }

        static CodeGenerator()
        {
            var dbConfig = ConfigHelper.GetValue<DbConfig>("DbConfig");
            var dbType = (DataType)Enum.Parse(typeof(DataType), dbConfig.DbType);
            appDb = new FreeSqlBuilder()
                .UseConnectionString(dbType, dbConfig.Connection)
                .UseAutoSyncStructure(false) //自动同步实体结构到数据库，只有CRUD时才会生成表
                .Build();

            solutionDir = GetSolutionRoot();
        }

        /// <summary>
        /// 变更数据库
        /// </summary>
        /// <param name="connStr"></param>
        /// <param name="dbTypeStr"></param>
        public static void ChangeDataBase(string connStr, string dbTypeStr, string dataBase)
        {
            CodeGenerator.dataBase = dataBase;
            var dbType = (DataType)Enum.Parse(typeof(DataType), dbTypeStr);
            appDb = new FreeSqlBuilder()
                .UseConnectionString(dbType, connStr)
                .UseAutoSyncStructure(false)
                .Build();
        }

        public static void Generate(string moduleName = "Home", bool ifExsitedCovered = false)
        {
            var tables = appDb.DbFirst.GetTablesByDatabase(dataBase);
            var tableList = string.Join(',', tables.Select(t => t.Name).ToArray());
            Generate(tableList, moduleName, ifExsitedCovered);
        }

        /// <summary>
        /// 代码生成器入口方法
        /// </summary>
        /// <param name="moduleName">模块</param>
        /// <param name="tableList">要生成代码的表</param>
        /// <param name="ifExsitedCovered">是否替换现有文件，为true时替换</param>
        public static void Generate(string tableList, string moduleName = "Home", bool ifExsitedCovered = false)
        {

            if (string.IsNullOrEmpty(tableList))
            {
                return;
            }
            var tables = tableList.Split(',');
            foreach (var table in tables)
            {
                var dbTableInfo = appDb.DbFirst.GetTableByName(table);
                if (dbTableInfo != null)
                {
                    GenerateSingle(dbTableInfo, moduleName, ifExsitedCovered);
                }
            }
        }

        public static void GenerateSingle(DbTableInfo tableInfo, string moduleName, bool ifExsitedCovered)
        {
            var tableName = tableInfo.Name;
            string[] tableNameTempArry = tableName.Split("_");
            string modleNameClass = tableNameTempArry[tableNameTempArry.Length - 1];
            string modelcontent = string.Empty;
            string formcontent = string.Empty;
            string listcontent = string.Empty;
            foreach (var dbFieldInfo in tableInfo.Columns)
            {
                string fieldName = dbFieldInfo.Name;
                string strDataType = SqlType2CsharpTypeStr(dbFieldInfo.DbTypeText, dbFieldInfo.IsNullable);
                modelcontent += "        /// <summary>\n";
                modelcontent += string.Format("        /// 设置或获取{0}\n", dbFieldInfo.Comment);
                modelcontent += "        /// </summary>\n";
                if (strDataType.ToLower() == "string")
                {
                    modelcontent += string.Format("        [Column(StringLength = {0})]\n", dbFieldInfo.MaxLength);
                }
                if (dbFieldInfo.IsPrimary)
                {
                    if (dbFieldInfo.IsIdentity)
                    {
                        modelcontent += " [Column(IsPrimary = true, IsIdentity = true)]\n\r";
                    }
                    else
                    {
                        modelcontent += " [Column(IsPrimary = true)]\n\r";
                    }
                }
                modelcontent += string.Format("        public {0} {1}", strDataType, fieldName);
                modelcontent += " { get; set; }\n\r";


                formcontent += $"<TextBlock Text=\"{{Binding [{fieldName}],Source={{x:Static core:LanguageHelper.Instance}}}}\" /><TextBox    Text=\"{{Binding EditData.{fieldName},UpdateSourceTrigger=PropertyChanged,ValidatesOnDataErrors=True}}\" />\r\n";
                listcontent += $"<DataGridTextColumn Header=\"{{Binding [{fieldName}],Source={{x:Static core:LanguageHelper.Instance}}}}\"  Binding=\"{{Binding {fieldName}}}\" IsReadOnly=\"True\"/>\r\n";
            }
            //GenerateModels(modleNameClass, modelcontent, moduleName, ifExsitedCovered);
            GenerateDTOModels(modleNameClass, moduleName, ifExsitedCovered);
            //GenerateControllers(modleNameClass, moduleName, ifExsitedCovered);
            GenerateViewModels(modleNameClass, moduleName, ifExsitedCovered);
            GenerateViews(modleNameClass, moduleName, formcontent, listcontent, ifExsitedCovered);
            GenerateViewModels(modleNameClass, moduleName, ifExsitedCovered, "Edit");
            GenerateViews(modleNameClass, moduleName, formcontent, listcontent, ifExsitedCovered, "Edit");

        }
        //private static void GenerateModels(string modelTypeName, string modelcontent, string moduleName, bool ifExsitedCovered)
        //{
        //    var servicesPath = Path.Combine(solutionDir, "Mohe.Scada.Entities", "Models", moduleName);
        //    if (!Directory.Exists(servicesPath))
        //    {
        //        Directory.CreateDirectory(servicesPath);
        //    }
        //    var fullPath = Path.Combine(servicesPath, modelTypeName) + ".cs";
        //    if (File.Exists(fullPath) && !ifExsitedCovered)
        //        return;
        //    var content = ReadTemplate("ModelsTemplate.txt");
        //    content = content
        //        .Replace("{ModuleName}", moduleName)
        //        .Replace("{ModelContent}", modelcontent)
        //        .Replace("{ModelTypeName}", modelTypeName);
        //    WriteAndSave(fullPath, content);

        //}
        private static void GenerateDTOModels(string modelTypeName, string moduleName, bool ifExsitedCovered)
        {
            var servicesPath = Path.Combine(solutionDir, "Desktop", "Mohe.Scada.DTOModels", "DTOModels", moduleName);
            if (!Directory.Exists(servicesPath))
            {
                Directory.CreateDirectory(servicesPath);
            }
            var fullPath = Path.Combine(servicesPath, modelTypeName) + "DTO.cs";
            if (File.Exists(fullPath) && !ifExsitedCovered)
                return;
            var content = ReadTemplate("DTOModelsTemplate.txt");
            content = content
                .Replace("{ModuleName}", moduleName)
                .Replace("{ModelTypeName}", modelTypeName);
            WriteAndSave(fullPath, content);

        }

        //private static void GenerateControllers(string modelTypeName, string moduleName, bool ifExsitedCovered)
        //{
        //    var servicesPath = Path.Combine(solutionDir, "Mohe.Scada.WebApi", "Controllers", moduleName);
        //    if (!Directory.Exists(servicesPath))
        //    {
        //        Directory.CreateDirectory(servicesPath);
        //    }
        //    var fullPath = Path.Combine(servicesPath, modelTypeName) + "Controller.cs";
        //    if (File.Exists(fullPath) && !ifExsitedCovered)
        //        return;
        //    var content = ReadTemplate("ControllersTemplate.txt");
        //    content = content
        //        .Replace("{ModuleName}", moduleName)
        //        .Replace("{ModelTypeName}", modelTypeName);
        //    WriteAndSave(fullPath, content);

        //}
        private static void GenerateViews(string modelTypeName, string moduleName, string formContent, string listContent, bool ifExsitedCovered, string method = "")
        {
            var servicesPath = Path.Combine(solutionDir, "Desktop", "Modules", moduleName, "Views");
            if (!Directory.Exists(servicesPath))
            {
                Directory.CreateDirectory(servicesPath);
            }
            var fullPath = Path.Combine(servicesPath, modelTypeName) + $"{method}View.xaml.cs";
            if (File.Exists(fullPath) && !ifExsitedCovered)
                return;
            var content = ReadTemplate($"{method}ViewTemplate_xaml_cs.txt");
            content = content
                .Replace("{ModuleName}", moduleName)
                .Replace("{ModelTypeName}", modelTypeName);
            WriteAndSave(fullPath, content);

            fullPath = Path.Combine(servicesPath, modelTypeName) + $"{method}View.xaml";
            if (File.Exists(fullPath) && !ifExsitedCovered)
                return;
            content = ReadTemplate($"{method}ViewTemplate_xaml.txt");
            content = content
                .Replace("{ViewContent}", listContent)
                .Replace("{FormContent}", formContent)
                .Replace("{ModuleName}", moduleName)
                .Replace("{ModelTypeName}", modelTypeName);

            WriteAndSave(fullPath, content);



        }
        private static void GenerateViewModels(string modelTypeName, string moduleName, bool ifExsitedCovered, string method = "")
        {
            var servicesPath = Path.Combine(solutionDir, "Desktop", "Modules", moduleName, "ViewModels");
            if (!Directory.Exists(servicesPath))
            {
                Directory.CreateDirectory(servicesPath);
            }
            var fullPath = Path.Combine(servicesPath, modelTypeName) + $"{method}ViewModel.cs";
            if (File.Exists(fullPath) && !ifExsitedCovered)
                return;
            var content = ReadTemplate($"{method}ViewModelTemplate_cs.txt");
            content = content
                .Replace("{ModuleName}", moduleName)
                .Replace("{ModelTypeName}", modelTypeName);
            WriteAndSave(fullPath, content);


        }


        private static string ReadTemplate(string templateName)
        {
            var currentAssembly = Assembly.GetExecutingAssembly();
            var content = string.Empty;
            using (var stream = currentAssembly.GetManifestResourceStream($"{currentAssembly.GetName().Name}.Template.{templateName}"))
            {
                if (stream != null)
                {
                    using var reader = new StreamReader(stream);
                    content = reader.ReadToEnd();
                }
            }
            return content;
        }

        private static void WriteAndSave(string fileName, string content)
        {
            //实例化一个文件流--->与写入文件相关联
            using var fs = new FileStream(fileName, FileMode.Create, FileAccess.Write);
            //实例化一个StreamWriter-->与fs相关联
            using var sw = new StreamWriter(fs);
            //开始写入
            sw.Write(content);
            //清空缓冲区
            sw.Flush();
            //关闭流
            sw.Close();
            fs.Close();

        }

        /// <summary>
        /// 将数据库类型转为系统类型。
        /// </summary>
        /// <param name="sqlType">数据库字段类型</param>
        /// <param name="isNullable">字段是否可空</param>
        /// <returns></returns>
        public static string SqlType2CsharpTypeStr(string sqlType, bool isNullable = false)
        {
            if (string.IsNullOrEmpty(sqlType))
                throw new ArgumentNullException(nameof(sqlType));
            var val = string.Empty;
            var allowNull = false;
            switch (sqlType.ToLower())
            {
                case "bit":
                    val = "bool";
                    break;
                case "int":
                    val = "int";
                    break;
                case "smallint":
                    val = "short";
                    break;
                case "bigint":
                    val = "long";
                    break;
                case "tinyint":
                    val = "bool";
                    break;

                case "binary":
                case "image":
                case "varbinary":
                    val = "byte[]";
                    allowNull = true;
                    break;

                case "decimal":
                    val = "decimal";
                    break;
                case "numeric":
                case "money":
                case "smallmoney":
                    val = "decimal";
                    break;

                case "float":
                    val = "float";
                    break;
                case "real":
                    val = "Single";
                    break;

                case "datetime":
                    val = "DateTime";
                    break;
                case "smalldatetime":
                case "timestamp":
                    val = "DateTime";
                    break;

                case "uniqueidentifier":
                    val = "Guid";
                    break;
                case "Variant":
                    val = "object";
                    allowNull = true;
                    break;

                case "text":
                    val = "string";
                    allowNull = true;
                    break;
                case "ntext":
                    val = "string";
                    allowNull = true;
                    break;
                case "char":
                    val = "string";
                    allowNull = true;
                    break;
                case "nchar":
                    val = "string";
                    allowNull = true;
                    break;
                case "varchar":
                    val = "string";
                    allowNull = true;
                    break;
                case "nvarchar":
                    val = "string";
                    allowNull = true;
                    break;
                default:
                    val = "string";
                    allowNull = true;
                    break;
            }
            if (isNullable && !allowNull)
                return val + "?";
            return val;
        }
    }
}
